using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

using SlimDX;

using miciv;
using micivCore.Data;
using miciv.Rendering;
using micivCore.DataSource;
using miciv.Misc;

namespace micivCore.Player
{
    public class LocalPlayer : PlayerBase
    {
        #region UpdateInputHandler helpers

        protected delegate void UpdateInputHandler();
        protected interface IPendingUpdateInputAction
        {
            UpdateInputHandler Do(ref Stack<UpdateInputHandler> _stckUpdateInputs, UpdateInputHandler _currentUpdateInput);
        }

        protected class PushPendingUpdateInputAction : IPendingUpdateInputAction
        {
            protected UpdateInputHandler m_updateInput;

            public PushPendingUpdateInputAction(UpdateInputHandler _updateInput)
            {
                this.m_updateInput = _updateInput;
            }

            #region IPendingUpdateInputAction Membres

            public UpdateInputHandler Do(ref Stack<UpdateInputHandler> _stckUpdateInputs, UpdateInputHandler _currentUpdateInput)
            {
                _stckUpdateInputs.Push(_currentUpdateInput);
                return this.m_updateInput;
            }

            #endregion
        }

        protected class PopPendingUpdateInputAction : IPendingUpdateInputAction
        {
            public PopPendingUpdateInputAction()
            {

            }

            #region IPendingUpdateInputAction Membres

            public UpdateInputHandler Do(ref Stack<UpdateInputHandler> _stckUpdateInputs, UpdateInputHandler _currentUpdateInput)
            {
                return _stckUpdateInputs.Pop();
            }

            #endregion
        }

        #endregion

        protected Stack<UpdateInputHandler> m_stckUpdateInputs;
        protected UpdateInputHandler m_currentUpdateInput;
        protected MapCoords m_selectedMapCoord;
        protected List<IPendingUpdateInputAction> m_lstPendingUpdateInputActions;
        protected IUIManager m_uiManager;
        protected bool m_bUpdatingInput;
        protected UnitTechniqueDataSource m_unitTechniquesDataSource;
        protected IUnitTechnique m_activeTechnique;
        protected IUnit m_activeUnit;

        public LocalPlayer(CoreContext _coreContext, Level.Level _level, PlayerDescription _desc)
            : base(_coreContext, _level, _desc)
        {
            this.m_stckUpdateInputs = new Stack<UpdateInputHandler>();
            this.m_selectedMapCoord = new MapCoords(0, 0);
            this.m_currentUpdateInput = this.UpdateInputDefault;
            this.m_lstPendingUpdateInputActions = new List<IPendingUpdateInputAction>();
            this.m_uiManager = this.m_coreContext.GetData<IUIManager>("UIManager");
            this.m_bUpdatingInput = false;
            this.m_unitTechniquesDataSource = new UnitTechniqueDataSource();
            this.m_activeTechnique = null;
            this.m_activeUnit = null;
        }

        override public bool Initialize(XmlElement _xmlPlayer)
        {
            if (false != base.Initialize(_xmlPlayer))
            {
                return true;
            }
            return false;
        }

        override public void Activate()
        {
            this.m_coreContext.GetData<DataSourceManager>("DataSourceManager").Add("unittechniquelist", this.m_unitTechniquesDataSource);
        }

        override public void Update()
        {
            if (null != this.m_currentUpdateInput)
            {
                this.m_bUpdatingInput = true;
                this.m_currentUpdateInput();
                this.m_bUpdatingInput = false;
            }

            // now update this.m_currentUpdateInput based on pending UpdateInput actions.
            foreach (IPendingUpdateInputAction action in this.m_lstPendingUpdateInputActions)
            {
                this.m_currentUpdateInput = action.Do(ref this.m_stckUpdateInputs, this.m_currentUpdateInput);
            }
            this.m_lstPendingUpdateInputActions.Clear();
        }

        override public void Deactivate()
        {
            this.m_coreContext.GetData<DataSourceManager>("DataSourceManager").Remove("unittechniquelist");
        }

        override public void Release()
        {

        }

        protected void UpdateInputDefault()
        {
            MouseInput mi = this.m_coreContext.GetData<MouseInput>("MouseInput");
            Module module = this.m_coreContext.GetData<Module>("micivCore.Module");
            // use 1st autorized renderer context to convert screen to world coordinates
            // it's a bit hacky because this renderer context may not be the right one for the conversion
            Vector3 f3World = module.RestrictedRendererContextes[0].Camera.Unproject(mi.X, mi.Y);

            // unselected current map tile
            this.MapCommunicator.SetVisibleMapTile(this.m_selectedMapCoord, "selected", false);

            // search new selected map tile based on mouse cursor position
            this.MapCommunicator.ComputeWorldToMapCoord(f3World.X, f3World.Y, out this.m_selectedMapCoord);
            this.MapCommunicator.SetVisibleMapTile(this.m_selectedMapCoord, "selected", true);

            // basic left-click selection support
            if (false != mi.JustReleasedButtons[0])
            {
                MapTile mapTile = this.MapCommunicator.GetMapTile(this.m_selectedMapCoord);
                if (null != mapTile)
                {
                    if (1 == mapTile.Units.Count)
                    {
                        if (mapTile.Units[0].Owner == this)
                        {
                            // setup data source with unit available techniques
                            this.m_unitTechniquesDataSource.Unit = mapTile.Units[0];
                            this.m_unitTechniquesDataSource.Update();
                            // launch unit technique selection ui
                            this.m_uiManager.ActivateScene("unittechniqueselectionmenu", true, true);
                            // switch to appropriate update inpute handler
                            this.SetUpdateInput(this.UpdateInputSelectUnitTechnique);
                        }
                    }
                    else if (1 < mapTile.Units.Count)
                    {
                        // setup a data source with available units
                        //this.m_unitDataSource.Units = mapTile.Units;
                        //this.m_unitDataSource.Update();
                        // launch units selection ui
                        //this.m_uiManager.ActivateScene("unitsselectectionmenu", true, true);
                        // switch to appropriate update inpute handler
                        //this.SetUpdateInput(this.UpdateInputSelectUnit);
                    }
                }
            }
        }

        protected void UpdateInputSelectUnitTechnique()
        {
            if ((null != this.m_activeTechnique) && (null != this.m_activeUnit))
            {
                if (false == this.m_activeTechnique.Apply(this.m_activeUnit))
                {
                    this.m_activeTechnique = null;
                    this.m_activeUnit = null;
                    this.PopPreviousUpdateInput();
                }
            }
        }

        protected void UpdateInputSelectUnit()
        {
        }

        protected void SetUpdateInput(UpdateInputHandler _inputHandler)
        {
            if (false != this.m_bUpdatingInput)
            {
                // since this method can (and surely will) be called during UpdateInput
                // it's wise to not modify this.m_currentUpdateInput
                // therefore a push request action is created and stored for a futur use at appropriate safe time.
                this.m_lstPendingUpdateInputActions.Add(new PushPendingUpdateInputAction(_inputHandler));
            }
            else
            {
                this.m_stckUpdateInputs.Push(this.m_currentUpdateInput);
                this.m_currentUpdateInput = _inputHandler;
            }
        }

        protected void PopPreviousUpdateInput()
        {
            if (false != this.m_bUpdatingInput)
            {
                // since this method can (and surely will) be called during UpdateInput
                // it's wise to not modify this.m_currentUpdateInput
                // therefore a pop request action is created and stored for a futur use at appropriate safe time.
                this.m_lstPendingUpdateInputActions.Add(new PopPendingUpdateInputAction());
            }
            else
            {
                this.m_currentUpdateInput = this.m_stckUpdateInputs.Pop();
            }
        }

        public void ScriptExecuteUnitTechnique(string _unitID, string _techniqueID)
        {
            IUnit unit = this.CoreContext.GetData<UnitManager>("UnitManager").Get(_unitID);
            foreach (IUnitTechnique technique in unit.Techniques)
            {
                if (_techniqueID == technique.ID)
                {
                    if (false != technique.Apply(unit))
                    {
                        this.m_activeTechnique = technique;
                        this.m_activeUnit = unit;
                    }
                    break;
                }
            }
        }

        protected void ApplyTechniqueJobCompleted(bool _bResult)
        {
        }

        public void ScriptCancelAction()
        {
            this.m_uiManager.PopPreviousScene();
            this.PopPreviousUpdateInput();
        }
    }
}
